#!/usr/bin/python
# 作者:龚华
# 2023年07月15日15时53分36秒
# -*- coding:utf-8 -*-
import time
from os import listdir

from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt


# 1准备数据：从文本文件中解析数据
def image2vector(fileName):
    """
    32*32的二进制图像转换为1*1024的向量
    :param fileName:
    :return: 1*1024的向量
    """
    returnVect = zeros((1, 1024))  # 初始化一个1*1024的矩阵，值全为0，用于存储一个32*32的图像，是.txt格式文件
    fr = open(fileName)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0, i * 32 + j] = int(lineStr[j])
    return returnVect


def test_image2vector():
    testVector = image2vector('digits/testDigits/0_13.txt')
    print(testVector[0, 0:31])
    print(testVector[0, 32:64])


def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


# 测试算法：用k-近邻算法识别手写数字
def handwritingClassTest():
    hwLabels = []
    traningList = listdir('digits/trainingDigits/')  # 导入os的listdir包
    m = len(traningList)
    traningMat = zeros((m, 1024))  # m*1024矩阵，m个样本，一个样本1024特征

    # 1.处理训练集的文件

    # 批处理文件目录内文件，每一个文件转为向量，最后形成m*1024矩阵
    # 文字类型数据转化为数值型矩阵
    for i in range(m):  # 读取每一个样本,既是一个txt文件,(0~9)-(0-99).txt
        filenameStr = traningList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        # 标签
        hwLabels.append(classNumStr)

        # 调用图像转化函数
        file_name = './digits/trainingDigits/' + filenameStr
        traningMat[i, :] = image2vector(file_name)

    # 2.处理训练集的文件
    testList = listdir('digits/testDigits/')

    errorCount = 0.0
    mTest = len(testList)
    for i in range(mTest):
        filenameStr = testList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        # 测试集是没有标签，标签是要预测
        # hwLabels.append(classNameStr)

        # 调用图像转化函数
        vectorUnderTest = image2vector('./digits/testDigits/%s' % filenameStr)

        # 测试集，只需一个样本样本的测试
        classifierResult = classify0(vectorUnderTest, traningMat, hwLabels, 5)  # k=3前三的标签
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))

        if (classifierResult != classNumStr):
            errorCount += 1.0

    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))


# classdif0函数的改造
def handwritingClassTest_rebuild():
    """
    改造classdif0函数
    优点：结构清晰，可读性高
    缺点：handwritingClassTest()只需要两个for循环，而handwritingClassTest_rebuild()需要三个for循环
    :return:
    """
    hwLabels = []
    traningList = listdir('digits/trainingDigits/')  # 导入os的listdir包
    m = len(traningList)
    traningMat = zeros((m, 1024))  # m*1024矩阵，m个样本，一个样本1024特征

    # 1.处理训练集的文件

    # 批处理文件目录内文件，每一个文件转为向量，最后形成m*1024矩阵
    # 文字类型数据转化为数值型矩阵
    for i in range(m):  # 读取每一个样本,既是一个txt文件,(0~9)-(0-99).txt
        filenameStr = traningList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        # 标签
        hwLabels.append(classNumStr)

        # 调用图像转化函数
        file_name = './digits/trainingDigits/' + filenameStr
        traningMat[i, :] = image2vector(file_name)

    # 2.处理训练集的文件
    testList = listdir('digits/testDigits/')

    mTest = len(testList)
    testMat = zeros((mTest, 1024))  # mTest*1024矩阵，m个样本，一个样本1024特征
    hwTestLabels = []
    for i in range(mTest):
        filenameStr = testList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        hwTestLabels.append(classNumStr)  # 进行标签的存储，与预测结果进行比较

        # 调用图像转化函数

        file_name = './digits/trainingDigits/' + filenameStr
        testMat[i, :] = image2vector(file_name)

    # 3、测试集，只需一个样本样本的测试
    errorCount = 0.0
    for i in range(mTest):
        # 测试集，只需一个样本样本的测试
        classifierResult = classify0(testMat[i], traningMat, hwLabels, 3)  # k=3前三的标签
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, hwTestLabels[i]))

        if (classifierResult != hwTestLabels[i]):
            errorCount += 1.0

    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))


# 参数测试
def handwritingClassTest_rankKnum(kNum=3):
    hwLabels = []
    traningList = listdir('digits/trainingDigits/')  # 导入os的listdir包
    m = len(traningList)
    traningMat = zeros((m, 1024))  # m*1024矩阵，m个样本，一个样本1024特征

    # 1.处理训练集的文件

    # 批处理文件目录内文件，每一个文件转为向量，最后形成m*1024矩阵
    # 文字类型数据转化为数值型矩阵
    for i in range(m):  # 读取每一个样本,既是一个txt文件,(0~9)-(0-99).txt
        filenameStr = traningList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        # 标签
        hwLabels.append(classNumStr)

        # 调用图像转化函数
        file_name = './digits/trainingDigits/' + filenameStr
        traningMat[i, :] = image2vector(file_name)

    # 2.处理训练集的文件
    testList = listdir('digits/testDigits/')

    errorCount = 0.0
    mTest = len(testList)
    for i in range(mTest):
        filenameStr = testList[i]
        fileStr = filenameStr.split('.')[0]  # 每一类含100个样本
        classNumStr = int(fileStr.split('_')[0])  # 共10个类

        # 测试集是没有标签，标签是要预测
        # hwLabels.append(classNameStr)

        # 调用图像转化函数
        vectorUnderTest = image2vector('./digits/testDigits/%s' % filenameStr)

        # 测试集，只需一个样本样本的测试
        classifierResult = classify0(vectorUnderTest, traningMat, hwLabels, kNum)  # k=3前三的标签

        if (classifierResult != classNumStr):
            errorCount += 1.0

    return errorCount, errorCount / float(mTest)


# 参数测试2
def handwritingTestKnum():
    errorRateDict = dict()
    errorCountDict = dict()
    spendTime = dict()
    for i in range(1, 10):
        print('\nstart test k=%d' % i)
        start = time.time()
        errorCount, errorRate = handwritingClassTest_rankKnum(i)
        end = time.time()
        errorCountDict[i] = errorCount
        errorRateDict[i] = errorRate
        spendTime[i] = end - start
    rankRate = sorted(errorRateDict.items(), key=lambda x: x[1])  # 按照errorRate排序
    rankCount = sorted(errorCountDict.items(), key=lambda x: x[1])  # 按照errorCount排序
    print()
    print('errorRate:\n', rankRate)
    print('errorCount:\n', rankCount)
    print('spendTime:\n', spendTime)


if __name__ == '__main__':
    # 测试image2vector函数
    # test_image2vector()

    # 测试手写识别系统
    # handwritingClassTest()

    # 改造classdif0函数，测试
    # handwritingClassTest_rebuild()

    # 参数测试
    handwritingTestKnum()
